
#ifndef WINVER
#  define WINVER 0x0500
#endif

#undef STRICT

#include <windows.h>

#include "../../epsilon.h"

#include "../../window.h"
#include "window_win32.h"

#include "../../EpsilonInput/keyboard.h"
#include "../../EpsilonInput/mouse.h"

#define eKeyboard Epsilon::Keyboard
#define eMouse    Epsilon::Mouse

bool
eWindowWin32::create (const eWindowParameters& parameters, eWindow* window)
{
  initialized = false;

  this->window = window;


  if (keyboard == NULL)
    keyboard = new eKeyboard ();

  if (mouse == NULL)
    mouse = new eMouse ();


  const eWindowParametersWin32& parameters_win32 =
      (const eWindowParametersWin32&) parameters;

  if (parameters_win32.instance == NULL)
    hInstance = GetModuleHandle (NULL);
  else
    hInstance = parameters_win32.instance;

  WindowProc = parameters_win32.window_proc;


  WNDCLASS wc;            // Windows Class Structure

  resize (parameters.window_width, parameters.window_height);

  wndRect.rcWindow.left   = wndRect.rcClient.left;
  wndRect.rcWindow.right  = wndRect.rcClient.right;

  wndRect.rcWindow.top    = wndRect.rcClient.top;
  wndRect.rcWindow.bottom = wndRect.rcClient.bottom;

  if (! hInstance)
    hInstance       = GetModuleHandle (NULL);                // Grab An Instance For Our Window

  wc.style          = CS_DBLCLKS | CS_OWNDC;                 // Don't Redraw On Move...
  wc.lpfnWndProc    = eWindowWin32::GlobalWindowProc;        // WndProc Handles Messages
  wc.cbClsExtra     = 0;                                     // No Extra Window Data
  wc.cbWndExtra     = 0;                                     // No Extra Window Data
  wc.hInstance      = hInstance;                             // Set The Instance
  wc.hIcon          = LoadIcon   (NULL, IDI_WINLOGO);        // Load The Default Icon
  wc.hCursor        = NULL;                                  // Don't set a cursor because we're setting our own
  wc.hbrBackground  = (HBRUSH) GetStockObject (BLACK_BRUSH); // ...
  wc.lpszMenuName   = NULL;                                  // We Don't Want A Menu

  _tcscpy (WindowClass, _T ("Epsilon - Window (0)"));
  wc.lpszClassName  = WindowClass;                         // Set The Class Name

  for (unsigned int iWindowNumber = 0; ! RegisterClass (&wc); iWindowNumber++)
  {
    // 1410 == Class already exists...
    if (GetLastError () != 1410) {
      eMessageBoxEx (_T ("ERROR"), _T ("Failed to register the window class."), E_MB_OK | E_MB_ICONEXCLAMATION);
      eSetLastError (EPSILON_ERROR_FAIL_REGISTER_WINDOW_CLASS);
      return false;
    } else {
      _stprintf (WindowClass, _T ("Epsilon - Window (%d)"), iWindowNumber);
      wc.lpszClassName  = WindowClass;
    }
  }

  memset (&dmOriginal, 0, sizeof (DEVMODE));
  dmOriginal.dmSize = sizeof (DEVMODE);
  EnumDisplaySettings (NULL, ENUM_CURRENT_SETTINGS, &dmOriginal);

  /*
  if (parameters.fullscreen)
  {
    dwExStyle     = WS_EX_APPWINDOW;                    // Window Extended Style
    dwStyle       = WS_POPUP;                           // Windows Style
  }
  else
  */
  {
    dwExStyle = WS_EX_APPWINDOW     | WS_EX_WINDOWEDGE; // Window Extended Style
    dwStyle   = WS_OVERLAPPEDWINDOW | WS_VISIBLE;       // Windows Style
  }

  AdjustWindowRectEx ((tagRECT *)&wndRect.rcWindow, dwStyle, false, dwExStyle);    // Determine the window rect required to yield the
                                                                                   // requested *CLIENT* dimensions.

  long adjustedWindowWidth  = wndRect.rcWindow.right  - wndRect.rcWindow.left;
  long adjustedWindowHeight = wndRect.rcWindow.bottom - wndRect.rcWindow.top;

  if (! (hWndRender = CreateWindowEx ( dwExStyle,                             // Extended Style For The Window
                                       WindowClass,                           // Class Name
                   //                    (parameters.parent_window ?
                                            //"Viewport" :                      // Window Title
                                            window->title,//),
                                       WS_CLIPSIBLINGS |                      // Required Window Style
                                       WS_CLIPCHILDREN |                      // Required Window Style
                                       dwStyle,                               // Selected Window Style
                                       0, 0,                                  // Window Position
                                       adjustedWindowWidth,  
                                       adjustedWindowHeight, 
                                       NULL,//parameters.parent_window,         // Possible Parent?
                                       NULL,                                  // No Menu
                                       hInstance,                             // Instance
                                       window )))                             // Don't Pass Anything To WM_CREATE
  {
    // Something went wrong, we need to kill the window!
    eMessageBoxEx   (_T ("ERROR"), _T ("Window creation failed!"), E_MB_OK | E_MB_ICONEXCLAMATION);
    eSetLastError (EPSILON_ERROR_CANNOT_CREATE_WINDOW);
    return false;
  }

  initialized = true;

  update_rect ();

  return true;
}

bool
eWindowWin32::destroy (void)
{
  initialized = false;

  // TODO
  return true;
}



LRESULT CALLBACK
eWindowWin32::GlobalWindowProc ( HWND    hWnd,     // Window Handle
                                 UINT    uMsg,     // Message
                                 WPARAM  wParam,   // wParam
                                 LPARAM  lParam )  // lParam
{
  eWindow* ParentWindow = NULL;

  switch (uMsg)
  {
    // Set this window's this* so we can pass other
    // messages to the proper window callback...
    case WM_CREATE:
      ParentWindow = reinterpret_cast<eWindow *>(((LPCREATESTRUCT)lParam)->lpCreateParams);
      SetWindowLong (hWnd, GWL_USERDATA, reinterpret_cast<long>(ParentWindow));
      break;
    case WM_DESTROY:
      break;
  }

  // Figure out the eWindow* associated with this
  // window and then send it its message!
  
  ParentWindow = reinterpret_cast<eWindow *>(GetWindowLong(hWnd, GWL_USERDATA));
  if (ParentWindow) {
    if (uMsg == WM_DESTROY)
      ParentWindow->internals.WindowProc = NULL;

    return ParentWindow->internals.InternalWindowProc ( hWnd,
                                                        uMsg,
                                                        wParam,
                                                        lParam );
  }

  // Pass the unhandled messages down the line to other message hooks.
  return DefWindowProc (hWnd, uMsg, wParam, lParam);
}


class eKeyboardWin32 {
public:
  static eKeyboard::Key EpsilonKey (WPARAM winKey) {
    struct WinKeyConvert {
      WPARAM         WinCode;
      eKeyboard::Key Key;
    };

    static const WinKeyConvert WinToEpsilon [] = {
      { 'A', eKeyboard::TextKey (eKeyboard::TextKeys::A) },
      { 'B', eKeyboard::TextKey (eKeyboard::TextKeys::B) },
      { 'C', eKeyboard::TextKey (eKeyboard::TextKeys::C) },
      { 'D', eKeyboard::TextKey (eKeyboard::TextKeys::D) },
      { 'E', eKeyboard::TextKey (eKeyboard::TextKeys::E) },
      { 'F', eKeyboard::TextKey (eKeyboard::TextKeys::F) },
      { 'G', eKeyboard::TextKey (eKeyboard::TextKeys::G) },
      { 'H', eKeyboard::TextKey (eKeyboard::TextKeys::H) },
      { 'I', eKeyboard::TextKey (eKeyboard::TextKeys::I) },
      { 'J', eKeyboard::TextKey (eKeyboard::TextKeys::J) },
      { 'K', eKeyboard::TextKey (eKeyboard::TextKeys::K) },
      { 'L', eKeyboard::TextKey (eKeyboard::TextKeys::L) },
      { 'M', eKeyboard::TextKey (eKeyboard::TextKeys::M) },
      { 'N', eKeyboard::TextKey (eKeyboard::TextKeys::N) },
      { 'O', eKeyboard::TextKey (eKeyboard::TextKeys::O) },
      { 'P', eKeyboard::TextKey (eKeyboard::TextKeys::P) },
      { 'Q', eKeyboard::TextKey (eKeyboard::TextKeys::Q) },
      { 'R', eKeyboard::TextKey (eKeyboard::TextKeys::R) },
      { 'S', eKeyboard::TextKey (eKeyboard::TextKeys::S) },
      { 'T', eKeyboard::TextKey (eKeyboard::TextKeys::T) },
      { 'U', eKeyboard::TextKey (eKeyboard::TextKeys::U) },
      { 'V', eKeyboard::TextKey (eKeyboard::TextKeys::V) },
      { 'W', eKeyboard::TextKey (eKeyboard::TextKeys::W) },
      { 'X', eKeyboard::TextKey (eKeyboard::TextKeys::X) },
      { 'Y', eKeyboard::TextKey (eKeyboard::TextKeys::Y) },
      { 'Z', eKeyboard::TextKey (eKeyboard::TextKeys::Z) },

      { '1', eKeyboard::TextKey (eKeyboard::TextKeys::One)   },
      { '2', eKeyboard::TextKey (eKeyboard::TextKeys::Two)   },
      { '3', eKeyboard::TextKey (eKeyboard::TextKeys::Three) },
      { '4', eKeyboard::TextKey (eKeyboard::TextKeys::Four)  },
      { '5', eKeyboard::TextKey (eKeyboard::TextKeys::Five)  },
      { '6', eKeyboard::TextKey (eKeyboard::TextKeys::Six)   },
      { '7', eKeyboard::TextKey (eKeyboard::TextKeys::Seven) },
      { '8', eKeyboard::TextKey (eKeyboard::TextKeys::Eight) },
      { '9', eKeyboard::TextKey (eKeyboard::TextKeys::Nine)  },
      { '0', eKeyboard::TextKey (eKeyboard::TextKeys::Zero)  },

      { VK_OEM_PLUS,   eKeyboard::TextKey (eKeyboard::TextKeys::Equals) },
      { VK_OEM_MINUS,  eKeyboard::TextKey (eKeyboard::TextKeys::Minus)  },
      { VK_OEM_6,      eKeyboard::TextKey (eKeyboard::TextKeys::RightBracket) },
      { VK_OEM_4,      eKeyboard::TextKey (eKeyboard::TextKeys::LeftBracket) },
      { VK_RETURN,     eKeyboard::MiscKey (eKeyboard::MiscKeys::Enter) },
      { VK_OEM_7,      eKeyboard::TextKey (eKeyboard::TextKeys::Quote) },
      { VK_OEM_1,      eKeyboard::TextKey (eKeyboard::TextKeys::SemiColon) },
      { VK_OEM_5,      eKeyboard::TextKey (eKeyboard::TextKeys::BackSlash) },
      { VK_OEM_COMMA,  eKeyboard::TextKey (eKeyboard::TextKeys::Comma) },
      { VK_OEM_2,      eKeyboard::TextKey (eKeyboard::TextKeys::Slash) },
      { VK_OEM_PERIOD, eKeyboard::TextKey (eKeyboard::TextKeys::Period) },
      { VK_TAB,        eKeyboard::MiscKey (eKeyboard::MiscKeys::Tab ) },
      { VK_SPACE,      eKeyboard::MiscKey (eKeyboard::MiscKeys::Space) },
      { VK_OEM_3,      eKeyboard::TextKey (eKeyboard::TextKeys::WHATTHEHELL) }, // `) ?
      { VK_BACK,       eKeyboard::MiscKey (eKeyboard::MiscKeys::Backspace) },
      { VK_ESCAPE,     eKeyboard::MiscKey (eKeyboard::MiscKeys::Escape) },

      { VK_DECIMAL,  eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_Period)   },
      { VK_MULTIPLY, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_Multiply) },
      { VK_ADD,      eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_Plus)     },
      { VK_DIVIDE,   eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_Divide)   },

////	    { 0x4C, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_Enter) },

	    { VK_SUBTRACT, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_Minus)    },

////	    { 0x51, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_Equals) },

      { VK_NUMPAD0, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_0) },
      { VK_NUMPAD1, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_1) },
      { VK_NUMPAD2, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_2) },
      { VK_NUMPAD3, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_3) },
      { VK_NUMPAD4, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_4) },
      { VK_NUMPAD5, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_5) },
      { VK_NUMPAD6, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_6) },
      { VK_NUMPAD7, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_7) },
      { VK_NUMPAD8, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_8) },
      { VK_NUMPAD9, eKeyboard::MiscKey (eKeyboard::MiscKeys::NumPad_9) },

      { VK_F1,  eKeyboard::MiscKey (eKeyboard::MiscKeys::F1)  },
      { VK_F2,  eKeyboard::MiscKey (eKeyboard::MiscKeys::F2)  },
      { VK_F3,  eKeyboard::MiscKey (eKeyboard::MiscKeys::F3)  },
      { VK_F4,  eKeyboard::MiscKey (eKeyboard::MiscKeys::F4)  },
      { VK_F5,  eKeyboard::MiscKey (eKeyboard::MiscKeys::F5)  },
      { VK_F6,  eKeyboard::MiscKey (eKeyboard::MiscKeys::F6)  },
      { VK_F7,  eKeyboard::MiscKey (eKeyboard::MiscKeys::F7)  },
      { VK_F8,  eKeyboard::MiscKey (eKeyboard::MiscKeys::F8)  },
      { VK_F9,  eKeyboard::MiscKey (eKeyboard::MiscKeys::F9)  },
      { VK_F10, eKeyboard::MiscKey (eKeyboard::MiscKeys::F10) },
      { VK_F11, eKeyboard::MiscKey (eKeyboard::MiscKeys::F11) },
      { VK_F12, eKeyboard::MiscKey (eKeyboard::MiscKeys::F12) },

      { VK_SNAPSHOT, eKeyboard::MiscKey (eKeyboard::MiscKeys::Print) },
      { VK_PAUSE,    eKeyboard::MiscKey (eKeyboard::MiscKeys::Pause) },

      { VK_SCROLL, eKeyboard::MiscKey (eKeyboard::MiscKeys::ScrollLock) },

      { VK_INSERT, eKeyboard::MiscKey (eKeyboard::MiscKeys::Insert)   },
      { VK_DELETE, eKeyboard::MiscKey (eKeyboard::MiscKeys::Delete)   },
      { VK_HOME,   eKeyboard::MiscKey (eKeyboard::MiscKeys::Home)     },
      { VK_END,    eKeyboard::MiscKey (eKeyboard::MiscKeys::End)      },
      { VK_PRIOR,  eKeyboard::MiscKey (eKeyboard::MiscKeys::PageUp)   },
      { VK_NEXT,   eKeyboard::MiscKey (eKeyboard::MiscKeys::PageDown) },

      { VK_LEFT,  eKeyboard::MiscKey (eKeyboard::MiscKeys::Left)  },
      { VK_RIGHT, eKeyboard::MiscKey (eKeyboard::MiscKeys::Right) },
      { VK_DOWN,  eKeyboard::MiscKey (eKeyboard::MiscKeys::Down)  },
      { VK_UP,    eKeyboard::MiscKey (eKeyboard::MiscKeys::Up)    }
   };

     for (unsigned int i = 0; i < sizeof (WinToEpsilon) / sizeof (WinKeyConvert); i++)
     {
       if (WinToEpsilon [i].WinCode == winKey)
         return WinToEpsilon [i].Key;
     }

     return eKeyboard::Key (0, eKeyboard::_NUM_KEY_TYPES);
  }
};

void
print_modifier_combo (WPARAM& keys)
{
  if (keys & Epsilon::Keyboard::ModifierKeys::Fn)
    printf ("(Fn) ");
  if (keys & Epsilon::Keyboard::ModifierKeys::NumLock)
    printf ("(Num Lock) ");
  if (keys & Epsilon::Keyboard::ModifierKeys::CapsLock)
    printf ("(Caps Lock) ");

  if (keys & Epsilon::Keyboard::ModifierKeys::Control)
    printf ("(Control) ");
  if (keys & Epsilon::Keyboard::ModifierKeys::Alt)
    printf ("(Option) ");
  if (keys & Epsilon::Keyboard::ModifierKeys::Command)
    printf ("(Command) ");
  if (keys & Epsilon::Keyboard::ModifierKeys::Shift)
    printf ("(Shift) ");
  printf ("\n");
}


// We intentionally neglect giving this function a DLL symbol...
// -- It is NEVER to be used outside of Epsilon's core code!
LRESULT CALLBACK
eWindowWin32::InternalWindowProc ( HWND    hWnd,     // Window Handle
                                   UINT    uMsg,     // Message
                                   WPARAM  wParam,   // wParam
                                   LPARAM  lParam )  // lParam
{
  switch (uMsg)               // Pre-Process important window messages
  {
    case WM_CREATE:
    {
      hWndRender = hWnd;

      break;
    }

    case WM_DESTROY:
    {
     // if (IsInitialized ())
////        destroy ();

      /// We need to stop the message pump after we finish processing this
      /// message. Calling DefWindowProc (at the end of this procedure) is
      /// undefined when given a handle to a window that doesn't exist...
      if (WindowProc)
        return (WindowProc) (hWnd, uMsg, wParam, lParam);

      /// Don't allow DefWindowProc to be called!!!
      return 0;

      break;
    }

    case WM_SYSCOMMAND:       // Handle System Commands
    {
      switch (wParam)         // Check the command type
      {
        case SC_SCREENSAVE:   // Don't allow the Screen Saver to start
        case SC_MONITORPOWER: // Don't allow the monitor to enter power saving
          return 0;           // Don't delegate this message any further

#if 0
        // Handle Minimize Events
        case SC_MINIMIZE:
        {
          if (! window->Minimize ())
            return 0;
        } break;

        // Handle Maximize Events
        case SC_MAXIMIZE:
        {
          if (! window->Maximize ())
            return 0;
        } break;

        // Handle Restore Events
        case SC_RESTORE:
        {
          if (! window->Restore ())
            return 0;
        } break;
#endif
      }
      break;
    }

    case WM_SIZE:             // Resize The OpenGL Window
    {
      //
      // Without this check, we may end up making calls to OpenGL functions
      // before the render context is even created.
      //
      //  * Since the window is created first, and receives a WM_SIZE message
      //    on creation.
      //
//      if (! IsInitialized ())
//        break;


      // LoWord = Width, HiWord = Height
/////      resize (LOWORD (lParam), HIWORD (lParam));

      update_rect ();

      if (window->wndListener)
        window->wndListener->OnResize (wndRect);
      else if (window->wndResizeCallback)
        window->wndResizeCallback (window, wndRect);

      return 0;               // Don't delegate this message any further
      break;
    }

    case WM_MOVE:             // Move the OpenGL window
    {
      update_rect ();

      if (window->wndListener)
        window->wndListener->OnMove (wndRect);
      else if (window->wndMoveCallback)
        window->wndMoveCallback (window, wndRect);

//      if (! render.hdc)
//        return 0;

      // Cheap hack to simulate vertical / horizontal redraw (PFD flags) on
      // move... Not perfect by any means, but better than the alternative,
      // which is flickering text on resize!
//      render.flip ();

      return 0;
      break;
    }

    case WM_KEYDOWN:         // Handle key presses
    case WM_SYSKEYDOWN:
    {
      /* Ignore the repeated messages sent by Windows... */
      bool repeat = (lParam >> 30) & 0x1; /* Bit 31 = last key state */

      if (repeat == false) {
        eKeyboard::Key key;

        switch (wParam)
        {
          case VK_SHIFT:
            key = eKeyboard::Key (eKeyboard::ModifierKeys::Shift, eKeyboard::Modifier);
            break;
          case VK_CONTROL:
            key = eKeyboard::Key (eKeyboard::ModifierKeys::Control, eKeyboard::Modifier);
            break;
          case VK_MENU:
            key = eKeyboard::Key (eKeyboard::ModifierKeys::Alt, eKeyboard::Modifier);
            break;
          case VK_LWIN:
          case VK_RWIN:
            key = eKeyboard::Key (eKeyboard::ModifierKeys::Command, eKeyboard::Modifier);
            break;
          case VK_NUMLOCK:
            key = eKeyboard::Key (eKeyboard::ModifierKeys::NumLock, eKeyboard::Modifier);
            break;
          case VK_CAPITAL:
            key = eKeyboard::Key (eKeyboard::ModifierKeys::CapsLock, eKeyboard::Modifier);
            break;

          default:
            key = eKeyboardWin32::EpsilonKey (wParam);
            break;
        }

        if (keyboard)
        keyboard->PressKey (key);
      }

      break;
    }

    case WM_KEYUP:           // Handle key releases
    case WM_SYSKEYUP:
    {
      eKeyboard::Key key;

      switch (wParam)
      {
        case VK_SHIFT:
          key = eKeyboard::Key (eKeyboard::ModifierKeys::Shift, eKeyboard::Modifier);
          break;
        case VK_CONTROL:
          key = eKeyboard::Key (eKeyboard::ModifierKeys::Control, eKeyboard::Modifier);
          break;
        case VK_MENU:
          key = eKeyboard::Key (eKeyboard::ModifierKeys::Alt, eKeyboard::Modifier);
          break;
        case VK_LWIN:
        case VK_RWIN:
          key = eKeyboard::Key (eKeyboard::ModifierKeys::Command, eKeyboard::Modifier);
          break;
        case VK_NUMLOCK:
          key = eKeyboard::Key (eKeyboard::ModifierKeys::NumLock, eKeyboard::Modifier);
          break;
        case VK_CAPITAL:
          key = eKeyboard::Key (eKeyboard::ModifierKeys::CapsLock, eKeyboard::Modifier);
          break;

        default:
          key = eKeyboardWin32::EpsilonKey (wParam);
          break;
      }

      if (keyboard)
        keyboard->ReleaseKey (key);

      break;
    }

    case WM_ACTIVATE:         // Handle window activation changes
    {
      /*
       CURSORINFO cursor;
       cursor.cbSize = sizeof (CURSORINFO);

       GetCursorInfo (&cursor);

      //  make sure it's an activation    && make sure the program isn't minimized
      if ( LOWORD (wParam) != WA_INACTIVE && !(BOOL)HIWORD (wParam) ) { 
        Activate ();        // No, we're good.
      }
      else { 
        // WARNING! If you deactivate here, the main loop won't be run,
        // and updates won't be sent to the server, causing a forced
        // logout...
        //
        //           -*- Find a beter solution! -*-
        //
//        Deactivate ();        // Yes, it's minimized, don't render anything
        // Disable mouse look when switching apps, or you'll be sorry!
        input.mouse.DisableMouseLook ();
      }
      */
      break;
    }

    case WM_MOUSELEAVE:
    {
      printf ("Mouse left...\n");

      ///keyboard->PressKey (eKeyboard::MiscKey (eKeyboard::MiscKeys::Tab));
      ///break;
      break;
    }

    case WM_MOUSEHOVER:
    {
      //TrackMouseEvent (&tme);

      break;
    }

    case WM_MOUSEMOVE:
    {
      int x = LOWORD (lParam);
      int y = HIWORD (lParam);

      //x += rect ().rcClient.left;
      //y += rect ().rcClient.top;

      //static int num = 0;
      //printf ("Mouse Moved (%d)...\n", ++num);// (%f, %f)  [Delta: (%f, %f)\n", pos_rel.x, pos_rel.y,  delta.x, delta.y);

      mouse->MoveCursor (x, y);

      break;
    }

    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    {
      /* TODO: Do we really need to trigger a mouse move event when
                 a button is pressed? ... */
//      mouse->MoveCursor (LOWORD (lParam), HIWORD (lParam));

      if (uMsg == WM_LBUTTONDOWN)
        mouse->PressButton   (eMouse::Buttons::Left);
      else
        mouse->ReleaseButton (eMouse::Buttons::Left);

      break;
    }

    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    {
      /* TODO: Do we really need to trigger a mouse move event when
                 a button is pressed? ... */
//      mouse->MoveCursor (LOWORD (lParam), HIWORD (lParam));

      if (uMsg == WM_RBUTTONDOWN)
        mouse->PressButton   (eMouse::Buttons::Right);
      else
        mouse->ReleaseButton (eMouse::Buttons::Right);

      break;
    }

    // DO NOT FORGET TO HAND THESE DOWN TO THE *REST* OF THE APPLICATION!
  }

  //
  // Mouse Enter / Leave detection
  //
  POINT cursor_pos;
  GetCursorPos (&cursor_pos);

  eWindowRect wndRect = window->getRect ();

  /// TODO: Make this into an Epsilon mouse event, that can be listened for...
  if (GetForegroundWindow () != hWnd ||
      cursor_pos.x < wndRect.rcClient.left   ||
      cursor_pos.x > wndRect.rcClient.right  ||
      cursor_pos.y > wndRect.rcClient.bottom ||
      cursor_pos.y < wndRect.rcClient.top) {
    show_cursor (true);
  } else {
    show_cursor (false);
  }

  if (WindowProc != NULL)
    if (! (WindowProc) (hWnd, uMsg, wParam, lParam))
      return 0;

  // Pass the unhandled messages down the line to other message hooks.
  return DefWindowProc (hWnd, uMsg, wParam, lParam);
}



void
eWindowWin32::Think (void)
{
  MSG  msg; // Windows Message Structure

  while (true) // Run until no messages are waiting
  {
    // Is There A Message Waiting?
    if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
    {
      TranslateMessage (&msg);    // Translate The Message
      DispatchMessage  (&msg);    // Dispatch The Message
    } else {
      return;
    }
  }
}




void
eWindowWin32::move (const int& left, const int& top)
{
  // NOTE: This moves the Window to the specified coords (the client region may
  //       be offset by a couple of pixels depending on the window style.
  if (initialized)
    SetWindowPos (hWndRender, NULL, left, top, 0, 0, SWP_NOSIZE);
}

void
eWindowWin32::move_client (const int& left, const int& top)
{
  // Compensate for window borders, and move the client rect to the specified
  // position.
  if (initialized) {
    update_rect ();

    int x_offset = wndRect.rcWindow.left - wndRect.rcClient.left;
    int y_offset = wndRect.rcWindow.top  - wndRect.rcClient.top;

    SetWindowPos (hWndRender, NULL, left + x_offset, top + y_offset, 0, 0, SWP_NOSIZE);
  }
}

void
eWindowWin32::resize (const int& x,    const int& y)
{
  if (initialized)
    SetWindowPos (hWndRender, NULL, 0, 0, x, y, SWP_NOMOVE);
}

void
eWindowWin32::resize_client (const int& x,    const int& y)
{
  // Set the window rect (includes borders) to match the current client rect.
  memcpy (&wndRect.rcWindow, &wndRect.rcClient, sizeof (eRect));
  
  // Resize the client rect
  wndRect.rcWindow.right  = wndRect.rcWindow.left + x;
  wndRect.rcWindow.bottom = wndRect.rcWindow.top  + y;

  // Adjust the window rect to compensate for borders
  AdjustWindowRectEx ((tagRECT *)&wndRect.rcWindow, dwStyle, false, dwExStyle);

  // Calculate the required number of pixels to resize the WINDOW to get the
  // desired CLIENT rect.
  int adj_x = wndRect.rcWindow.right  - wndRect.rcWindow.left;
  int adj_y = wndRect.rcWindow.bottom - wndRect.rcWindow.top;

  if (initialized)
    SetWindowPos (hWndRender, NULL, 0, 0, adj_x, adj_y, SWP_NOMOVE);
}

/// TODO
void eWindowWin32::show (int)
{
  return;
}

/// TODO
bool eWindowWin32::recreate (const eWindowParameters&)
{
  return false;
}


void
eWindowWin32::update_rect (void)
{
  // Don't care about invalid update_rect's when the window isn't initialized.
  if (! initialized)
    return;

  BOOL bIsWindow = IsWindow (hWndRender);
  Assert (bIsWindow != FALSE, _T ("eWindowWin32::update_rect () called with an invalid window handle!"));

  if (! bIsWindow) {
    eSetLastError (EPSILON_ERROR_INVALID_WINDOW_HANDLE);
    return;
  }

  WINDOWINFO info;
  info.cbSize = sizeof (info);
  GetWindowInfo (hWndRender, &info);

  memcpy (&wndRect.rcClient, &info.rcClient, sizeof (wndRect.rcClient));
  memcpy (&wndRect.rcWindow, &info.rcWindow, sizeof (wndRect.rcWindow));

  wndRect.cxWindowBorders = info.cxWindowBorders;
  wndRect.cyWindowBorders = info.cyWindowBorders;
}


const eWindowHandle
eWindowWin32::getNativeWindowHandle (void)
{
  return eWindowHandle ((DWORD)hWndRender, window);
}


void
eWindowWin32::show_cursor (bool show)
{
  if (cursorShown != show) {
    cursorShown = show;
    ::ShowCursor (cursorShown); // Win32 function
  }
}

void
eWindowWin32::set_cursor_pos (int x, int y)
{
  SetCursorPos ( x + wndRect.rcClient.left,
                 y + wndRect.rcClient.top );
}